using System;
using System.Collections.Generic;
using System.IO;
using log4net;
using log4net.Config;
using log4net.Core;

namespace TeamTasker.Utils
{
    /// <summary>  
    ///<para>Wrapper for log4net logger.</para>  
    /// </summary>  
    public class Log
    {
        /// <summary>  
        /// Property name which is used to keep log path value.
        /// </summary>  
        protected static string PROPERTY_LOG_PATH = "LogFileDir";

        /// <summary>  
        /// Log4net logger instance
        /// </summary>  
        protected static ILog logger = LogManager.GetLogger("");

        /// <summary>
        /// Max size of logs folder to keep - older logs must 
        /// be periodically deleted to avoid disk space insufficiency.
        /// </summary>
        protected static int maxStorageSize = -1;

        /// <summary>
        /// Log path value.
        /// </summary>
        protected static string logPath;

        /// <summary>
        /// Initialization indicator. 
        /// </summary>
        protected static bool isInitialized;

        /// <summary>
        /// Private constructor. 
        /// </summary>
        private Log()
        {

        }

        public static bool IsDebugEnabled()
        {
            CheckInit();
            return logger.IsDebugEnabled;
        }

        public static bool IsInfoEnabled()
        {
            CheckInit();
            return logger.IsInfoEnabled;
        }

        public static bool IsWarnEnabled()
        {
            CheckInit();
            return logger.IsWarnEnabled;
        }

        public static bool IsErrorEnabled()
        {
            CheckInit();
            return logger.IsErrorEnabled;
        }

        public static bool IsFatalEnabled()
        {
            CheckInit();
            return logger.IsFatalEnabled;
        }

        public static void Debug(object message)
        {
            CheckInit();
            CheckSpace();
            logger.Debug(message);
        }

        public static void Info(object message)
        {
            CheckInit();
            CheckSpace();
            logger.Info(message);
        }

        public static void Warning(object message)
        {
            CheckInit();
            CheckSpace();
            logger.Warn(message);
        }

        public static void Warning(object message, Exception exception)
        {
            CheckInit();
            CheckSpace();
            logger.Warn(message, exception);
        }

        public static void Error(object message)
        {
            CheckInit();
            CheckSpace();
            logger.Error(message);
        }

        public static void Error(object message, Exception exception)
        {
            CheckInit();
            CheckSpace();
            logger.Error(message, exception);
        }

        public static void Fatal(object message)
        {
            CheckInit();
            CheckSpace();
            logger.Fatal(message);
        }

        public static void Fatal(object message, Exception exception)
        {
            CheckInit();
            CheckSpace();
            logger.Fatal(message, exception);
        }

        protected static void InitLogLevel(string levelName)
        {
            if (levelName != null && string.IsNullOrEmpty(levelName) == false)
            {
                Level definedLevel = LogManager.GetRepository().LevelMap[levelName];
                if (definedLevel != null)
                {
                    LogManager.GetRepository().Threshold = definedLevel;
                }
            }
        }

        protected static void InitLogPath(string aLogPath)
        {
            Validate.NotBlank(aLogPath, "Log output path is not specified");
            ThreadContext.Properties[PROPERTY_LOG_PATH] = aLogPath;
            logPath = aLogPath;
        }

        public static void Init(string logConfigPath, string logLevelName, 
                                string logOutputPath, int maxStorageSizeMb)
        {
            InitLogPath(logOutputPath);
            XmlConfigurator.Configure(new FileInfo(logConfigPath));
            InitLogLevel(logLevelName);
            maxStorageSize = maxStorageSizeMb;
            isInitialized = true;
        }

        protected static void CheckInit()
        {
            if (!isInitialized)
            {
                throw new ApplicationException("Loging subsystem is not initialized yet.");
            }
        }

        private static int spaceCheckCounter;

        protected static void CheckSpace()
        {
            ++spaceCheckCounter;
            if (spaceCheckCounter > 5000)
            {
                DeleteOldLogs();
                spaceCheckCounter = 0;
            }
        }

        protected static void DeleteOldLogs()
        {
            try
            {
                DirectoryInfo dir = new DirectoryInfo(LogPath);
                FileInfo[] files = dir.GetFiles();
                Array.Sort(files, new FileByAccessTimeComparer());
                long totalSize = 0L;
                bool fDeleteFiles = false;
                foreach (FileInfo file in files)
                {
                    totalSize += file.Length;
                    if (fDeleteFiles == false && (totalSize >> 20) > maxStorageSize)
                    {
                        fDeleteFiles = true;
                    }
                    if (fDeleteFiles)
                    {
                        file.Delete();
                        logger.Info("Log file is deleted due to space limitations: " + file.Name);
                    }
                }
            }
            catch (Exception ex)
            {
                // what to do here?? supress or raise? 
            }
        }

        public static bool IsInitialized
        {
            get { return isInitialized; }
        }

        public static string LogPath
        {
            get { return logPath; }
        }

        public static string LogLevel
        {
            get { return LogManager.GetRepository().Threshold.DisplayName; }
        }
    }

    class FileByAccessTimeComparer : IComparer<FileInfo>
    {
        public int Compare(FileInfo x, FileInfo y)
        {
            long diff = y.LastWriteTime.Ticks - x.LastWriteTime.Ticks;
            if (diff < 0)
            {
                return -1;
            }
            if (diff > 0)
            {
                return 1;
            }
            return 0;
        }
    }
}